import { ISearchOptions } from '@xterm/addon-search';

import {
  Deferred,
  Disposable,
  Emitter,
  Event,
  OperatingSystem,
  debounce,
  IEventBus,
  URI,
} from '../../utils';

import {
  ICreateTerminalOptions,
  IShellLaunchConfig,
  ITerminalClient,
  ITerminalConnection,
  ITerminalController,
  ITerminalDataEvent,
  ITerminalExitEvent,
  ITerminalInternalService,
  ITerminalTitleChangeEvent,
  IWidget,
  TerminalOptions,
} from '../common';

import { AttachAddon, DEFAULT_COL, DEFAULT_ROW } from './terminal.addon';
import { XTerm } from './xterm';
import { TerminalClientAttachEvent } from '../../common/terminal';

export class TerminalClient extends Disposable implements ITerminalClient {
  // 工作区路径缓存，key 为 group id，value 为路径
  static WORKSPACE_PATH_CACHED: Map<string, string> = new Map();

  /** 属性定义 */
  private _uid: string; // 终端唯一 id
  private _terminalOptions: TerminalOptions; // 终端选项
  private _widget: IWidget; // 终端对应的 widget
  private _workspacePath: string; // 当前工作区路径
  /** 结束 */

  isTaskExecutor = false; // 是否为任务执行器
  taskId: string | undefined; // 任务 id

  /** 状态定义 */
  private _ready = false; // 终端是否已就绪
  private _attached: Deferred<void>; // 终端连接完成的 Promise
  private _firstStdout: Deferred<void>; // 首次输出的 Promise
  private _error: Deferred<void>; // 错误状态 Promise
  private _show: Deferred<void> | null; // 显示状态 Promise
  private _hasOutput = false; // 是否有输出
  private _areLinksReady = false; // 链接是否已准备好
  private _os: OperatingSystem; // 当前操作系统
  /** 结束 */

  // 服务依赖
  private internalService: ITerminalInternalService;
  private controller: ITerminalController;
  private eventBus: IEventBus;

  // 输入事件
  private _onInput = new Emitter<ITerminalDataEvent>();
  onInput: Event<ITerminalDataEvent> = this._onInput.event;

  // 输出事件
  private _onOutput = new Emitter<ITerminalDataEvent>();
  onOutput: Event<ITerminalDataEvent> = this._onOutput.event;

  // 退出事件
  private _onExit = new Emitter<ITerminalExitEvent>();
  onExit: Event<ITerminalExitEvent> = this._onExit.event;

  // 标题变更事件
  private _onTitleChange = new Emitter<ITerminalTitleChangeEvent>();
  onTitleChange: Event<ITerminalTitleChangeEvent> = this._onTitleChange.event;

  // 链接准备好事件
  private readonly _onLinksReady = new Emitter<ITerminalClient>();
  onLinksReady: Event<ITerminalClient> = this._onLinksReady.event;

  // 响应时间事件
  private readonly _onResponseTime = new Emitter<number>();
  private _attachAddon: AttachAddon; // 终端连接插件

  xterm: XTerm; // xterm 实例
  private _launchConfig: IShellLaunchConfig; // 启动配置

  constructor(
    internalService: ITerminalInternalService,
    controller: ITerminalController,
    eventBus: IEventBus
  ) {
    super();
    this.internalService = internalService;
    this.controller = controller;
    this.eventBus = eventBus;
    this.init();
  }

  // 初始化 xterm 及相关事件
  private async init() {
    this.xterm = new XTerm({});
    this.addDispose(this.xterm);

    // 监听终端错误
    this.addDispose(
      this.internalService.onError((error) => {
        if (error.launchConfig?.executable) {
          this.updateTerminalName({
            name: 'error: ' + error.launchConfig.executable,
          });
        } else {
          this.updateTerminalName({
            name: 'error',
          });
        }
      })
    );
    // 监听进程变更
    this.addDispose(
      this.internalService.onProcessChange((e) => {
        if (e.sessionId === this.id) {
          // @ts-ignore
          this.widget.processName.set(e.processName);
        }
      })
    );
  }

  // widget 显示时的处理
  private onWidgetShow() {
    if (this._show) {
      this._show.resolve();
      this._show = null;
    }
    this._layout();
  }

  // 绑定 widget 并初始化相关事件
  async setupWidget(widget: IWidget) {
    this._widget = widget;
    this._prepare();

    // widget 销毁时清理缓存
    this.addDispose(
      Disposable.create(() => {
        TerminalClient.WORKSPACE_PATH_CACHED.delete(widget.group.id);
      })
    );

    // 处理 widget 显示时机
    if (widget.show) {
      this.onWidgetShow();
    } else {
      this.addDispose(
        widget.onShow((status) => {
          if (status) {
            this.onWidgetShow();
          }
        })
      );
    }

    // 监听 widget 错误
    this.addDispose(
      widget.onError((status) => {
        if (status) {
          // fit 操作在对比行列没有发送变化的时候不会做任何操作，
          // 但是实际上是设置为 display none 了，所以手动 resize 一下
          // this._term.resize(1, 1);
        } else {
          this._error?.resolve();
          this._layout();
        }
      })
    );

    // 监听 widget 尺寸变化
    this.addDispose(
      widget.onResize(async () => {
        this._debounceResize();
      })
    );
  }

  // 初始化终端，包括启动配置、环境变量等
  async init2(widget: IWidget, options?: ICreateTerminalOptions) {
    this._uid = options?.id || widget.id;
    this.setupWidget(widget);

    if (!options || Object.keys(options).length === 0) {
      options = {
        id: this._uid,
      };
    }

    if (!options.cwd) {
      // 从 group 的第一个 widget 解析 cwd
      const group = widget.group;
      const widgets = group.widgets.get();
      if (widgets.length > 1 && widgets[0]) {
        const cwd = await this.internalService.getCwd(widgets[0].id);
        if (cwd) {
          options.cwd = cwd;
        }
      }
    }

    await this._checkWorkspace();

    const cwd =
      options.cwd ??
      (options?.config as IShellLaunchConfig)?.cwd ??
      this._workspacePath;

    const launchConfig = this.controller.convertProfileToLaunchConfig(
      options.config,
      cwd
    );
    this._launchConfig = launchConfig;
    if (this._launchConfig.__fromTerminalOptions) {
      this._terminalOptions = this._launchConfig.__fromTerminalOptions;
    }

    this.name = launchConfig.name || '';

    if (launchConfig.initialText) {
      this.xterm.raw.writeln(launchConfig.initialText);
    }
    if (!launchConfig.env) {
      launchConfig.env = {};
    }

    this._attachXterm();
    this._attachAfterRender();
  }

  // 终端名称变更事件
  protected _onNameChangeEmitter = new Emitter<string>();
  onNameChange = this._onNameChangeEmitter.event;

  protected _name = '';
  get name() {
    return this._name;
  }

  set name(name: string) {
    if (this._name !== name) {
      this._name = name;
      this._onNameChangeEmitter.fire(name);
    }
  }

  get term() {
    return this.xterm.raw;
  }

  get pid() {
    return this.internalService.getProcessId(this.id);
  }

  get launchConfig(): IShellLaunchConfig {
    return this._launchConfig;
  }

  get container() {
    return this.xterm.container;
  }

  get id() {
    return this._uid;
  }

  get widget() {
    return this._widget;
  }

  get ready() {
    return this._ready;
  }

  get attached() {
    return this._attached;
  }

  get firstOutput() {
    return this._firstStdout;
  }

  get show() {
    return this._show;
  }

  get areLinksReady(): boolean {
    return this._areLinksReady;
  }

  get os() {
    return this._os;
  }

  // 初始化并加载终端插件
  private _prepareAddons() {
    this._attachAddon = new AttachAddon();
    const onBeforeProcessEvent = this._attachAddon.onBeforeProcessData;

    this.addDispose([
      this._attachAddon,
      this._attachAddon.onData((data) => {
        this._onOutput.fire({ id: this.id, data });
      }),
      this._attachAddon.onExit((code) => {
        if (code !== 0) {
          // this.messageService.error(
          //   `terminal ${this.name}(${this._attachAddon.connection?.ptyInstance?.shellPath}) exited with non-zero code ${code}`,
          // );
        }
        this._onExit.fire({ id: this.id, code });
      }),

      this._attachAddon.onTime((delta) => {
        this._onResponseTime.fire(delta);
      }),
    ]);
    this.xterm.raw.loadAddon(this._attachAddon);
  }

  // 绑定 xterm 事件
  private _xtermEvents() {
    this.addDispose(
      this.xterm.raw.onResize((_event) => {
        if (this._hasOutput) {
          this._doResize();
        }
      })
    );
  }

  // 绑定 xterm、插件、链接管理器
  private _attachXterm() {
    this._prepareAddons();
    this._xtermEvents();
    this._areLinksReady = true;
    this._onLinksReady.fire(this);
  }

  // 执行终端 resize
  _doResize() {
    // TODO: debounce
    this.internalService.resize(
      this.id,
      this.xterm.raw.cols,
      this.xterm.raw.rows
    );
  }

  // 重新初始化状态
  _prepare() {
    this._attached?.reject('TerminalClient is Re-initialization');
    this._firstStdout?.reject('TerminalClient is Re-initialization');
    this._error?.reject('TerminalClient is Re-initialization');
    this._show?.reject('TerminalClient is Re-initialization');
    this._ready = false;
    this._hasOutput = false;
    this._attached = new Deferred<void>();
    this._show = new Deferred<void>();
    this._error = new Deferred<void>();
    this._firstStdout = new Deferred<void>();
    this._attachAddon?.setConnection(undefined, undefined);
    this.internalService.getOS().then((os) => {
      this._os = os;
    });
    const { dispose } = this.onOutput(() => {
      dispose();
      this._hasOutput = true;
      this._firstStdout.resolve();
    });
  }

  // 渲染后 attach 终端
  _attachAfterRender() {
    // 等待 widget 渲染后再 attach，尽可能在创建时获取到准确的宽高
    // requestAnimationFrame 在不可见状态下会丢失，所以一定要用 queueMicrotask
    queueMicrotask(() => {
      this._layout();
      this.attach();
      this.focus();
      if (!this.widget.show) {
        this._show?.promise.then(async () => {
          this._show = new Deferred<void>();
        });
      }
    });
  }

  private async _pickWorkspace() {
    const uriStr = 'file:///home/noodles/';
    const fsPath = new URI(uriStr).codeUri.fsPath;
    return fsPath;

    // if (this.workspace.isMultiRootWorkspaceOpened && !this._widget.recovery) {
    //   // 工作区模式下每次新建终端都需要用户手动进行一次路径选择
    //   const roots = this.workspace.tryGetRoots();
    //   const choose = await this.quickPick.show(
    //     roots.map((file) => new URI(file.uri).codeUri.fsPath),
    //     { placeholder: localize('terminal.selectCWDForNewTerminal') }
    //   );
    //   return choose;
    // } else if (this.workspace.workspace) {
    //   return new URI(this.workspace.workspace?.uri).codeUri.fsPath;
    // } else {
    //   return undefined;
    // }
  }

  /**
   * if we want open a terminal, we need a parameter: `cwd`
   * we don't care whether it valid. our backend service will check it.
   *
   * 如果当前工作区不存在，这里就会获得空，后端的逻辑中终端会打开用户的家目录。
   * 多工作区模式下用户没有选中任何一个工作区，也是会打开用户的家目录。
   */
  private async _checkWorkspace() {
    const widget = this._widget;

    console.log(
      '_checkWorkspace',
      widget.group.id,
      widget.group.name,
      TerminalClient.WORKSPACE_PATH_CACHED.has(widget.group.id)
    );

    if (TerminalClient.WORKSPACE_PATH_CACHED.has(widget.group.id)) {
      this._workspacePath = TerminalClient.WORKSPACE_PATH_CACHED.get(
        widget.group.id
      )!;
    } else {
      const choose = await this._pickWorkspace();

      console.log('choose workspace', choose);
      if (choose) {
        this._workspacePath = choose;
        TerminalClient.WORKSPACE_PATH_CACHED.set(
          widget.group.id,
          this._workspacePath
        );
      }
    }
  }

  // 检查 Terminal Client 对应的后端 Shell 进程是否存活
  public async checkHealthy() {
    return await this.internalService.check([this.id]);
  }

  // 输出终端已经被 Kill 的提醒
  public displayUnHealthyMessage() {}

  // 重置终端，重新初始化并 attach
  reset() {
    this._prepare();
    this._attachAfterRender();
  }

  // attach 终端，如果未就绪则执行 _doAttach
  private async attach() {
    if (!this._ready) {
      return this._doAttach();
    }
  }

  // 实际执行 attach 逻辑，连接终端后端
  private async _doAttach() {
    const sessionId = this.id;
    const { rows = DEFAULT_ROW, cols = DEFAULT_COL } = this.xterm.raw;

    let connection: ITerminalConnection | undefined;

    // 合并启动配置
    const finalLaunchConfig = {
      ...this._launchConfig,
      cwd: this._launchConfig?.cwd || this._workspacePath,
    };

    this._launchConfig = finalLaunchConfig;

    try {
      // 通过启动配置 attach 终端
      connection = await this.internalService.attachByLaunchConfig(
        sessionId,
        cols,
        rows,
        this._launchConfig
        // this.xterm
      );
    } catch (e) {
      console.error('_doAttach error:', e);
    }

    console.log(
      `TerminalClient ${this.id} attaching with connection:`,
      connection
    );

    this.resolveConnection(connection);
  }

  // 处理终端连接结果，设置相关状态
  resolveConnection(connection: ITerminalConnection | undefined) {

    if (!connection) {
      this._attached.reject('no connection while attaching terminal client');
      return;
    }

    this._attachAddon.setConnection(this.internalService, connection);
    const name = this.name || this._launchConfig.name || connection.name;
    if (name !== this.name) {
      this.name = name;
      this._onTitleChange.fire({ id: this.id, name });
    }
    this._ready = true;
    this._attached.resolve();
    this._widget.rename(this.name);

    this.eventBus.fire(new TerminalClientAttachEvent({ clientId: this.id }));

    this._firstStdout.promise.then(() => {
      this._doResize();
    });
  }

  // 检查终端是否就绪，未就绪则抛出异常
  private _checkReady() {
    if (!this._ready) {
      throw new Error('terminal client not ready');
    }
  }

  // 重新布局终端，适配容器尺寸
  private _layout() {
    // 如果 xterm 视图还没初始化，则先尝试初始化
    try {
      this._renderOnDemand();
      if (this.xterm.raw.element) {
        this.xterm.fit();
      }
    } catch (e) {
      // noop
    }
  }

  // 按需渲染 xterm 视图，避免重复渲染
  private _renderOnDemand() {
    // 避免重复创建 xterm 视图，后果是终端实例和视图不匹配，表现为整个卡住
    if (this.xterm.raw.element) {
      return;
    }
    // xterm 视图容器没准备好，取消渲染 xterm 视图
    if (!this._widget.element?.clientHeight) {
      return;
    }

    this._widget.element.appendChild(this.xterm.container);
    this.xterm.open();
    // 首次渲染且为当前选中的 client 时，聚焦
    // 等待数据更新、terminal 渲染完成，但是无需等待连接成功，体验上会更快一些
    setTimeout(() => {
      if (this.controller.activeClient?.id === this.id) {
        this.focus();
      }
    });
  }

  // 防抖终端 resize，100ms 内只触发一次
  @debounce(100)
  private _debounceResize() {
    this._layout();
  }

  // 聚焦终端
  focus() {
    return this.xterm.raw.focus();
  }

  // 清空终端内容
  clear() {
    this._checkReady();
    return this.xterm.raw.clear();
  }

  // 全选终端内容
  selectAll() {
    this._checkReady();
    return this.xterm.raw.selectAll();
  }

  // 粘贴文本到终端
  paste(text: string) {
    this._checkReady();
    return this.xterm.raw.paste(text);
  }

  // 查找下一个匹配内容
  findNext(text: string, searchOptions: ISearchOptions = {}) {
    this._checkReady();
    return this.xterm.findNext(text, searchOptions);
  }

  // 查找上一个匹配内容
  findPrevious(text: string, searchOptions: ISearchOptions = {}) {
    this._checkReady();
    return this.xterm.findPrevious(text, searchOptions);
  }

  // 获取搜索结果变更事件
  get onSearchResultsChange() {
    return this.xterm.onSearchResultsChange;
  }

  // 关闭搜索面板
  closeSearch() {
    this.xterm.closeSearch();
  }

  // 获取当前选中的内容
  getSelection() {
    this._checkReady();
    if (this.xterm.raw.hasSelection()) {
      return this.xterm.raw.getSelection();
    } else {
      return '';
    }
  }

  // 更新终端启动配置
  updateLaunchConfig(launchConfig: IShellLaunchConfig) {
    this._launchConfig = {
      ...this._launchConfig,
      ...launchConfig,
    };
  }

  // 更新终端名称
  updateTerminalName(options: { name: string }) {
    if (!this.name && !this._widget.name.get()) {
      this._widget.rename(options.name || this.name);
    }
  }

  // 发送文本到终端
  async sendText(message: string) {
    await this.internalService.sendText(this.id, message);
    this._onInput.fire({ id: this.id, data: message });
  }

  // 销毁终端客户端
  dispose(clear = true) {
    super.dispose();

    if (clear) {
      this.internalService.disposeById(this.id);
    }
  }
}

export class TerminalClientFactory {
  /**
   * 创建 terminal 实例最终都会调用该方法
   */
  static async createClient2(
    internalService: ITerminalInternalService,
    controller: ITerminalController,
    eventBus: IEventBus,
    widget: IWidget,
    options?: ICreateTerminalOptions
  ) {
    const client = new TerminalClient(internalService, controller, eventBus);
    await client.init2(widget, options);
    return client;
  }
}
